// Copyright 2010, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef SCRW_TESTING_CURL_TESTING_H_
#define SCRW_TESTING_CURL_TESTING_H_

#include <string>
#include <gtest/gtest.h>
#include "common.h"
#include "curl.h"

#ifdef _WIN32
# pragma warning(push)
# pragma warning(disable:4996)  // Function may be unsafe...
#endif


namespace scrw
{

namespace testing
{

class CurlTest : public ::testing::Test
{
  protected:
    typedef Curl::Handle::PerformResult CHPR;

    Curl curl;

    virtual void SetUp()
    {
        curl.set_default_referer("");
        curl.set_default_connect_timeout(1);
        curl.set_default_cookie("");
        curl.set_default_low_speed_limit(1000, 1);
        curl.set_default_user_agent("libcurl");
        curl.set_default_max_num_redirects(10);
    }


    virtual void TearDown()
    {
    }


    AsciiUrl GetUrl(const ::std::string& filename)
    {
        return kTestFilesURLPrefix + filename;
    }


    template<class TContainer>
    static bool is_equal(const TContainer& cont, const char* str)
    {
        return cont.size() == ::std::strlen(str) &&
               ::std::equal(cont.begin(), cont.end(), str);
    }


    static bool is_equal(const libv::CIString& ci_str,
                         const libv::CIString& str)
    {
        return ci_str == str;
    }


    static const ::std::string kTestFilesURLPrefix;

    // Адрес, которого не существует,
    // и нет невозможно соедениться с сервером, а не 404.
    static const ::std::string kNotExistingDomain;
};
// В папке проекта есть папка scrw_tests. Ее следует залить на какой-либо
// веб-сервер и указать путь к нему в этой переменной.
const ::std::string CurlTest::kTestFilesURLPrefix =
    "http://iu-9.info/upload/scrw_tests/";

const ::std::string CurlTest::kNotExistingDomain =
"http://boo666.bmstu.ru/";


class Filter
{
  public:
    enum { CONTINUE_DOWNLOAD = 1, NEED_MORE_DATA = 0, BREAK_DOWNLOAD = -1 };

    int operator()(const AsciiUrl&,
                   const MimeType&,
                   const Charset&,
                   const ::std::vector<char>&) const
    {
        return CONTINUE_DOWNLOAD;
    }
};

TEST_F(CurlTest, Precond)
{
    Curl::Handle not_inited_handle;
    EXPECT_THROW(not_inited_handle.set_user_agent("1"), libv::PrecondException);
    EXPECT_THROW(not_inited_handle.set_referer("1"), libv::PrecondException);
    EXPECT_THROW(not_inited_handle.set_connect_timeout(1),
                 libv::PrecondException);
    EXPECT_THROW(not_inited_handle.set_low_speed_limit(1, 1),
                 libv::PrecondException);
    EXPECT_THROW(not_inited_handle.set_cookie("1"), libv::PrecondException);
    EXPECT_THROW(not_inited_handle.Perform(GetUrl("default.php")),
                 libv::PrecondException);
    EXPECT_THROW(not_inited_handle.Perform(GetUrl("default.php"), Filter()),
                 libv::PrecondException);
}


TEST_F(CurlTest, HandleIsReferenceBased)
{
    Curl::Handle curl_handle = curl.GetHandle();
    Curl::Handle curl_handle2 = curl_handle;
    curl_handle2.set_cookie("test=scrw");
    Curl::Handle curl_handle3;
    curl_handle3 = curl_handle2;

    const CHPR r = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(r.data, "OK"));

    const CHPR r3 = curl_handle3.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(r3.data, "OK"));
}


TEST_F(CurlTest, Clone)
{
    Curl::Handle curl_handle = curl.GetHandle();
    Curl::Handle curl_handle2 = curl_handle.Clone();
    curl_handle2.set_cookie("test=scrw");

    const CHPR r = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(r.data, "Fail"));
}


TEST_F(CurlTest, CloneAndDeleteOriginal)
{
    Curl::Handle curl_handle;

    {
        Curl::Handle curl_handle2 = curl.GetHandle();
        curl_handle2.set_cookie("test=scrw");
        curl_handle = curl_handle2.Clone();
    }

    const CHPR r = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(r.data, "OK"));
}


TEST_F(CurlTest, LowLevelHandle)
{
    Curl::Handle curl_handle = curl.GetHandle();
    curl_easy_setopt(curl_handle.low_level_handle(),
                     CURLOPT_COOKIE, "test=scrw");
    const CHPR r = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(r.data, "OK"));
}


TEST_F(CurlTest, Simple)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl("default.php"));
    EXPECT_TRUE(is_equal(r.data, "OK"));
    EXPECT_TRUE(is_equal(r.mime_type, "text/html"));
}


// Очень нестабильный тест.
TEST_F(CurlTest, DISABLED_FTP)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform("ftp://ftp.yandex.ru/HEADER.txt");
    EXPECT_EQ('W', r.data[0]);
    EXPECT_TRUE(is_equal(r.mime_type, ""));
    EXPECT_TRUE(is_equal(r.charset, ""));
}


TEST_F(CurlTest, SimpleCantResolve)
{
    Curl::Handle curl_handle = curl.GetHandle();

    // Важно, что бы dns не редиректил несуществующие домены на 404, как
    // это, например, у яндекса.
    EXPECT_THROW(curl_handle.Perform(kNotExistingDomain), CurlException);
}


// Сайт zalil.ru не выдает информацию по протоколу HTTP1.1, если ему не послать
// необязательное сообщение ("Connection: keep-alive") в HTTP-заголовке.
// Все браузеры посылают его, а curl — нет, так как оно и так задано по
// умолчанию стандартом.
// Приходится добавлять это сообщение в дополнительные сообщения.
// @sa Curl::Handle::new_http_neader_extra()
TEST_F(CurlTest, ZalilRu)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform("http://zalil.ru/00000000");
    EXPECT_FALSE(r.data.empty());
}


TEST_F(CurlTest, MimeWithCharset)
{
    Curl::Handle curl_handle = curl.GetHandle();

    const ::std::string file = "xml_mime_with_charset=utf8.php";
    const CHPR r = curl_handle.Perform(GetUrl(file));
    EXPECT_TRUE(is_equal(r.mime_type, "text/xml"));
    EXPECT_TRUE(is_equal(r.charset, "UTF-8"));
}


TEST_F(CurlTest, MimeWithCharsetInQuotes)
{
    Curl::Handle curl_handle = curl.GetHandle();

    const ::std::string file = "xml_mime_with_charset=utf8_in_quotes.php";
    const CHPR r = curl_handle.Perform(GetUrl(file));
    EXPECT_TRUE(is_equal(r.mime_type, "text/xml"));
    EXPECT_TRUE(is_equal(r.charset, "UTF-8"));
}


TEST_F(CurlTest, MimeWithCharsetWithSemicolon)
{
    Curl::Handle curl_handle = curl.GetHandle();

    const ::std::string file = "xml_mime_with_charset=utf8_with_semicolon.php";
    const CHPR r = curl_handle.Perform(GetUrl(file));
    EXPECT_TRUE(is_equal(r.mime_type, "text/xml"));
    EXPECT_TRUE(is_equal(r.charset, "UTF-8"));
}


TEST_F(CurlTest, DifficultMime)
{
    Curl::Handle curl_handle = curl.GetHandle();

    const ::std::string file = "difficult_xml_mime_with_charset=utf8.php";
    const CHPR r = curl_handle.Perform(GetUrl(file));
    EXPECT_TRUE(is_equal(r.mime_type, "text/xml"));
    EXPECT_TRUE(is_equal(r.charset, "UTF-8"));
}


TEST_F(CurlTest, MultilineMime)
{
    const ::std::string filename = "multiline_xml_mime_with_charset=utf8.php";
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl(filename));
    EXPECT_TRUE(is_equal(r.mime_type, "text/xml"));
    EXPECT_TRUE(is_equal(r.charset, "UTF-8"));
}


TEST_F(CurlTest, BadMime)
{
    Curl::Handle curl_handle = curl.GetHandle();
    EXPECT_THROW(curl_handle.Perform(GetUrl("bad_mime_no_end_quote.php")),
                 CurlException);

    const ::std::string t = "bad_mime_forbidden_char_in_type.php";
    EXPECT_THROW(curl_handle.Perform(GetUrl(t)), CurlException);

    const ::std::string s = "bad_mime_forbidden_char_in_subtype.php";
    EXPECT_THROW(curl_handle.Perform(GetUrl(s)), CurlException);

    const ::std::string c = "bad_charset_forbidden_char.php";
    EXPECT_THROW(curl_handle.Perform(GetUrl(c)), CurlException);
}



TEST_F(CurlTest, DISABLED_HTTPDefaultMime)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl("empty_mime.php"));
    EXPECT_TRUE(is_equal(r.mime_type, "application/octet-stream"));
}


TEST_F(CurlTest, Redirect)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl("redirect.php"));
    EXPECT_TRUE(is_equal(r.data, "OK"));
    EXPECT_TRUE(is_equal(r.mime_type, "text/html"));
}


TEST_F(CurlTest, RedirectNewMime)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl("redirect_xml_mime.php"));
    EXPECT_TRUE(is_equal(r.mime_type, "text/xml"));
}


TEST_F(CurlTest, DISABLED_RedirectHTTPDefaultMime)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl("redirect_empty_mime.php"));
    EXPECT_TRUE(is_equal(r.mime_type, "application/octet-stream"));
}


TEST_F(CurlTest, RedirectToFtp)
{
    Curl::Handle curl_handle = curl.GetHandle();
    // Редирект на ftp.
    EXPECT_THROW(curl_handle.Perform(GetUrl("redirect_no_mime.php")),
                 CurlException);
}


TEST_F(CurlTest, CookieIsSaved)
{
    Curl::Handle curl_handle = curl.GetHandle();
    curl_handle.Perform(GetUrl("set_cookie.php"));

    const CHPR r = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(r.data, "OK"));
}


TEST_F(CurlTest, CookieRewriting)
{
    Curl::Handle curl_handle = curl.GetHandle();
    curl_handle.set_cookie("test=scrw");
    curl_handle.set_cookie("");

    const CHPR r = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(r.data, "Fail"));
}


TEST_F(CurlTest, CookiesClear)
{
    Curl::Handle curl_handle = curl.GetHandle();
    curl_handle.Perform(GetUrl("set_cookie.php"));
    curl_handle.set_cookie("test2=scrw");
    curl_handle.clear_cookies();  // удаляет только присланную

    const CHPR r = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(r.data, "Fail"));

    const CHPR r2 = curl_handle.Perform(GetUrl("cookie2.php"));
    EXPECT_TRUE(is_equal(r2.data, "OK"));
}


class BadFilter
{
  public:
    enum { CONTINUE_DOWNLOAD = 1, NEED_MORE_DATA = 0, BREAK_DOWNLOAD = -1,
           BAD_VALUE = -2 };
    BadFilter() {}

    int operator()(const AsciiUrl&,
                   const MimeType&,
                   const Charset&,
                   const ::std::vector<char>&) const
    {
        return BAD_VALUE;
    }
};


TEST_F(CurlTest, PerformWithBadFilter)
{
    const BadFilter filter;

    Curl::Handle curl_handle = curl.GetHandle();
    EXPECT_THROW(curl_handle.Perform(GetUrl("ok.txt"), filter), CurlException);
}


class DownloadAllFilter
{
  public:
    enum { CONTINUE_DOWNLOAD = 1, NEED_MORE_DATA = 0, BREAK_DOWNLOAD = -1 };

    DownloadAllFilter() : num_called_(0) {}

    int operator()(const AsciiUrl&,
                   const MimeType&,
                   const Charset&,
                   const ::std::vector<char>&) const
    {
        num_called_++;
        return CONTINUE_DOWNLOAD;
    }

    // Отладочная функция.
    size_t num_called() const { return num_called_; }
  private:
    mutable size_t num_called_;
};

TEST_F(CurlTest, PerformWithDownloadAllFilter)
{

    const DownloadAllFilter filter;

    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl("ok.txt"), filter);
    EXPECT_TRUE(is_equal(r.data, "OK"));
    EXPECT_TRUE(is_equal(r.mime_type, "text/plain"));
    EXPECT_EQ(1U, filter.num_called());

    const CHPR r2 = curl_handle.Perform(GetUrl("ok.html"), filter);
    EXPECT_TRUE(is_equal(r2.data, "OK"));
    EXPECT_TRUE(is_equal(r2.mime_type, "text/html"));
    EXPECT_EQ(2U, filter.num_called());
}


class BreakAllFilter
{
  public:
    enum { CONTINUE_DOWNLOAD = 1, NEED_MORE_DATA = 0, BREAK_DOWNLOAD = -1 };

    BreakAllFilter() : num_called_(0) {}

    int operator()(const AsciiUrl&,
                   const MimeType&,
                   const Charset&,
                   const ::std::vector<char>&) const
    {
        num_called_++;
        return BREAK_DOWNLOAD;
    }

    // Отладочная функция.
    size_t num_called() const { return num_called_; }

  private:
    mutable size_t num_called_;
};

TEST_F(CurlTest, PerformWithBreakAllFilter)
{
    const BreakAllFilter filter;

    Curl::Handle curl_handle = curl.GetHandle();

    EXPECT_THROW(curl_handle.Perform(GetUrl("ok.txt"), filter),
                 FileSkippedException);
    EXPECT_EQ(1U, filter.num_called());

    EXPECT_THROW(curl_handle.Perform(GetUrl("ok.html"), filter),
                FileSkippedException);
    EXPECT_EQ(2U, filter.num_called());
}


class NeedMoreDataContinueFilter
{
  public:
    explicit NeedMoreDataContinueFilter(const size_t file_size)
        : file_size_(file_size),
          num_called_(0) {}

    enum { CONTINUE_DOWNLOAD = 1, NEED_MORE_DATA = 0, BREAK_DOWNLOAD = -1 };

    int operator()(const AsciiUrl&,
                   const MimeType&,
                   const Charset&,
                   const ::std::vector<char>& data) const
    {
        num_called_++;
        if ( data.size() != file_size_ )
            return NEED_MORE_DATA;
        return CONTINUE_DOWNLOAD;
    }

    // Отладочная функция.
    size_t num_called() const { return num_called_; }

  private:
    size_t file_size_;
    mutable size_t num_called_;
};

TEST_F(CurlTest, PerformWithNeedMoreDataContinueFilter)
{
    const size_t file_size = 512 * 1024;
    const ::std::string file_name = "512kb.txt";
    const NeedMoreDataContinueFilter filter(file_size);

    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl(file_name), filter);
    EXPECT_EQ(file_size, r.data.size());
    EXPECT_TRUE(is_equal(r.mime_type, "text/plain"));
    EXPECT_LE(1U, filter.num_called());
}


class NeedMoreDataBreakFilter
{
  public:
    explicit NeedMoreDataBreakFilter(const size_t file_size)
        : file_size_(file_size),
           num_called_(0) {}

    enum { CONTINUE_DOWNLOAD = 1, NEED_MORE_DATA = 0, BREAK_DOWNLOAD = -1 };

    int operator()(const AsciiUrl&,
                   const MimeType&,
                   const Charset,
                   const ::std::vector<char>& data) const
    {
        num_called_++;
        if ( data.size() != file_size_ )
            return NEED_MORE_DATA;
        return BREAK_DOWNLOAD;
    }

    // Отладочная функция.
    size_t num_called() const { return num_called_; }

  private:
    size_t file_size_;
    mutable size_t num_called_;
};

TEST_F(CurlTest, PerformWithNeedMoreDataBreakFilter)
{
    const size_t file_size = 512 * 1024;
    const ::std::string file_name = "512kb.txt";
    const NeedMoreDataBreakFilter filter(file_size);

    Curl::Handle curl_handle = curl.GetHandle();
    EXPECT_THROW(curl_handle.Perform(GetUrl(file_name), filter),
                 FileSkippedException);
    EXPECT_LE(1U, filter.num_called());
}


TEST_F(CurlTest, SetConnectTimeout)
{
    Curl::Handle curl_handle = curl.GetHandle();
    curl_handle.set_connect_timeout(1);
    EXPECT_THROW(curl_handle.Perform(kNotExistingDomain), CurlException);
}


TEST_F(CurlTest, DefaultSetConnectTimeout)
{
    curl.set_default_connect_timeout(1);
    Curl::Handle curl_handle = curl.GetHandle();
    EXPECT_THROW(curl_handle.Perform(kNotExistingDomain), CurlException);
}


TEST_F(CurlTest, SetCookie)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR rfail = curl_handle.Perform(GetUrl("cookie.php"));
    curl_handle.set_cookie("");
    EXPECT_TRUE(is_equal(rfail.data, "Fail"));

    curl_handle.set_cookie("test=scrw");
    const CHPR rok = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(rok.data, "OK"));
}


TEST_F(CurlTest, SetMultipleCookie)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR rfail = curl_handle.Perform(GetUrl("cookie.php"));
    curl_handle.set_cookie("a=b;test=scrw");
    const CHPR rok = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(rok.data, "OK"));
}


TEST_F(CurlTest, SetDefaultCookie)
{
    curl.set_default_cookie("test=scrw");
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR rok = curl_handle.Perform(GetUrl("cookie.php"));
    EXPECT_TRUE(is_equal(rok.data, "OK"));
}


TEST_F(CurlTest, SetUserAgent)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR rfail = curl_handle.Perform(GetUrl("user_agent.php"));
    EXPECT_TRUE(is_equal(rfail.data, "Fail"));

    curl_handle.set_user_agent("scrw");
    const CHPR rok = curl_handle.Perform(GetUrl("user_agent.php"));
    EXPECT_TRUE(is_equal(rok.data, "OK"));
}


TEST_F(CurlTest, SetDefaultUserAgent)
{
    curl.set_default_user_agent("scrw");
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR rok = curl_handle.Perform(GetUrl("user_agent.php"));
    EXPECT_TRUE(is_equal(rok.data, "OK"));
}


TEST_F(CurlTest, SetReferer)
{
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR rfail = curl_handle.Perform(GetUrl("referer.php"));
    EXPECT_TRUE(is_equal(rfail.data, "Fail"));

    curl_handle.set_referer("scrw");
    const CHPR rok = curl_handle.Perform(GetUrl("referer.php"));
    EXPECT_TRUE(is_equal(rok.data, "OK"));
}


TEST_F(CurlTest, SetDefaultReferer)
{
    curl.set_default_referer("scrw");
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR rok = curl_handle.Perform(GetUrl("referer.php"));
    EXPECT_TRUE(is_equal(rok.data, "OK"));
}


TEST_F(CurlTest, SetLowSpeedLimit)
{
    // Тест исходит из того, что сервер не отдаст больше 256 кб за 1 секунду.
    // Если он проваливается, следует попробовать заменить загружаемый файл на
    // 512kb.txt или создать новый с приемлемым размером, так, что бы
    // загрузка документа проходила больше low_speed_time_secs секунд.

    const size_t low_speed_time_secs = 1;
    const size_t very_high_speed = 1 << 30;
    const size_t very_low_speed = 1;
    const ::std::string filename = "256kb.txt";
    Curl::Handle curl_handle = curl.GetHandle();
    curl_handle.set_low_speed_limit(very_high_speed, low_speed_time_secs);
    EXPECT_THROW(curl_handle.Perform(GetUrl(filename)), CurlException);

    curl_handle.set_low_speed_limit(very_low_speed, low_speed_time_secs);
    EXPECT_NO_THROW(curl_handle.Perform(GetUrl(filename)));
}


TEST_F(CurlTest, SetDefaultLowSpeedLimit)
{
    const size_t low_speed_time_secs = 1;
    const size_t very_high_speed = 1 << 30;
    const ::std::string filename = "256kb.txt";

    curl.set_default_low_speed_limit(
        very_high_speed, low_speed_time_secs);
    Curl::Handle curl_handle = curl.GetHandle();
    EXPECT_THROW(curl_handle.Perform(GetUrl(filename)), CurlException);
}


TEST_F(CurlTest, SetMaxNumRedirects)
{
    Curl::Handle curl_handle = curl.GetHandle();
    curl_handle.set_max_num_redirects(1);
    const CHPR r = curl_handle.Perform(GetUrl("redirect.php"));
    EXPECT_TRUE(is_equal(r.data, "OK"));
    EXPECT_TRUE(is_equal(r.mime_type, "text/html"));

    curl_handle.set_max_num_redirects(0);
    const CHPR r2 = curl_handle.Perform(GetUrl("redirect.php"));
    EXPECT_TRUE(is_equal(r2.data, ""));
}


TEST_F(CurlTest, SetDefaultMaxNumRedirects)
{
    curl.set_default_max_num_redirects(1);
    Curl::Handle curl_handle = curl.GetHandle();
    const CHPR r = curl_handle.Perform(GetUrl("redirect.php"));
    EXPECT_TRUE(is_equal(r.data, "OK"));
    EXPECT_TRUE(is_equal(r.mime_type, "text/html"));

    curl.set_default_max_num_redirects(0);
    Curl::Handle curl_handle2 = curl.GetHandle();
    const CHPR r2 = curl_handle2.Perform(GetUrl("redirect.php"));
    EXPECT_TRUE(is_equal(r2.data, ""));
}


}  // testing

}  // scrw


#ifdef _WIN32
# pragma warning(pop)
#endif


#endif  // SCRW_TEST_CURL_TEST_H_
